/*
** Haaf's Game Engine 1.8
** Copyright (C) 2003-2007, Relish Games
** hge.relishgames.com
**
** hge_tut08 - The Big Calm
*/


// Copy the files "font2.fnt", "font2.png",
// and "objects.png" from the folder "precompiled" to
// the folder with executable file. Also copy hge.dll
// to the same folder.


// smaller sun & moon, underwater
// moon shape, hide stars behind the moon


#include <math.h>

#include "../../include/hge.h"
#include "../../include/hgecolor.h"
#include "../../include/hgesprite.h"
#include "../../include/hgedistort.h"
#include "../../include/hgefont.h"

// Pointer to the HGE interface (helper classes require this to work)

HGE* hge = nullptr;
hgeFont* fnt = nullptr;

// Simulation constants

#define SCREEN_WIDTH	800
#define SCREEN_HEIGHT	600
#define NUM_STARS		100
#define SEA_SUBDIVISION 16

#define SKY_HEIGHT		(SCREEN_HEIGHT*0.6f)
#define STARS_HEIGHT	(SKY_HEIGHT*0.9f)
#define ORBITS_RADIUS	(SCREEN_WIDTH*0.43f)

uint32_t skyTopColors[] = {0xFF15092A, 0xFF6C6480, 0xFF89B9D0};
uint32_t skyBtmColors[] = {0xFF303E57, 0xFFAC7963, 0xFFCAD7DB};
uint32_t seaTopColors[] = {0xFF3D546B, 0xFF927E76, 0xFF86A2AD};
uint32_t seaBtmColors[] = {0xFF1E394C, 0xFF2F4E64, 0xFF2F4E64};

int seq[] = {0, 0, 1, 2, 2, 2, 1, 0, 0};

// Simulation resource handles

HTEXTURE texObjects;

hgeSprite* sky;
hgeSprite* sun;
hgeSprite* moon;
hgeSprite* glow;
hgeSprite* seaglow;
hgeSprite* star;

hgeDistortionMesh* sea;

hgeColor colWhite;

// Simulation state variables

float time; // 0-24 hrs
float speed; // hrs per second

int seq_id;
float seq_residue;

float starX[NUM_STARS]; // x
float starY[NUM_STARS]; // y
float starS[NUM_STARS]; // scale
float starA[NUM_STARS]; // alpha

float seaP[SEA_SUBDIVISION]; // phase shift array

hgeColor colSkyTop;
hgeColor colSkyBtm;
hgeColor colSeaTop;
hgeColor colSeaBtm;

hgeColor colSun;
hgeColor colSunGlow;
hgeColor colMoon;
hgeColor colMoonGlow;
hgeColor colSeaGlow;

float sunX, sunY, sunS, sunGlowS;
float moonX, moonY, moonS, moonGlowS;
float seaGlowX, seaGlowSX, seaGlowSY;

// Simulation methods

bool InitSimulation();
void DoneSimulation();
void UpdateSimulation();
void RenderSimulation();


///////////////////////// Implementation ///////////////////////////


bool frame_func() {
    // Process keys

    switch (hge->Input_GetKey()) {
    case HGEK_0: speed = 0.0f;
        break;
    case HGEK_1: speed = 0.1f;
        break;
    case HGEK_2: speed = 0.2f;
        break;
    case HGEK_3: speed = 0.4f;
        break;
    case HGEK_4: speed = 0.8f;
        break;
    case HGEK_5: speed = 1.6f;
        break;
    case HGEK_6: speed = 3.2f;
        break;
    case HGEK_7: speed = 6.4f;
        break;
    case HGEK_8: speed = 12.8f;
        break;
    case HGEK_9: speed = 25.6f;
        break;
    case HGEK_ESCAPE: return true;
    }

    // Update scene

    UpdateSimulation();

    return false;
}


bool render_func() {

    // Calculate display time

    const auto hrs = static_cast<int>(floorf(time));
    const auto tmp = (time - hrs) * 60.0f;
    const auto mins = static_cast<int>(floorf(tmp));
    const auto secs = static_cast<int>(floorf((tmp - mins) * 60.0f));

    // Render scene

    hge->Gfx_BeginScene();
    RenderSimulation();
    fnt->printf(7, 7, HGETEXT_LEFT, "Keys 1-9 to adjust simulation speed, 0 - real time\nFPS: %d",
                hge->Timer_GetFPS());
    fnt->printf(SCREEN_WIDTH - 50, 7, HGETEXT_LEFT, "%02d:%02d:%02d", hrs, mins, secs);
    hge->Gfx_EndScene();

    return false;
}


int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) {
    hge = hgeCreate(HGE_VERSION);

    // Set desired system states and initialize HGE

    hge->System_SetState(HGE_LOGFILE, "hge_tut08.log");
    hge->System_SetState(HGE_FRAMEFUNC, frame_func);
    hge->System_SetState(HGE_RENDERFUNC, render_func);
    hge->System_SetState(HGE_TITLE, "HGE Tutorial 08 - The Big Calm");
    hge->System_SetState(HGE_USESOUND, false);
    hge->System_SetState(HGE_WINDOWED, true);
    hge->System_SetState(HGE_SCREENWIDTH, SCREEN_WIDTH);
    hge->System_SetState(HGE_SCREENHEIGHT, SCREEN_HEIGHT);
    hge->System_SetState(HGE_SCREENBPP, 32);

    if (hge->System_Initiate()) {
        fnt = new hgeFont("font2.fnt");

        if (!InitSimulation()) {
            // If one of the data files is not found, display an error message and shutdown
            MessageBox(nullptr, "Can't load resources. See log for details.", "Error",
                       MB_OK | MB_ICONERROR | MB_APPLMODAL);
            hge->System_Shutdown();
            hge->Release();
            return 0;
        }

        hge->System_Start();

        DoneSimulation();
        delete fnt;
    }

    hge->System_Shutdown();
    hge->Release();
    return 0;
}


float GetTime() {
    SYSTEMTIME SysTime;

    GetLocalTime(&SysTime);
    float tmp = SysTime.wSecond;
    tmp = SysTime.wMinute + tmp / 60.0f;
    tmp = SysTime.wHour + tmp / 60.0f;

    return tmp;
}


bool InitSimulation() {
    // Load texture

    texObjects = hge->Texture_Load("objects.png");
    if (!texObjects) return false;

    // Create sprites

    sky = new hgeSprite(0, 0, 0, SCREEN_WIDTH, SKY_HEIGHT);
    sea = new hgeDistortionMesh(SEA_SUBDIVISION, SEA_SUBDIVISION);
    sea->SetTextureRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - SKY_HEIGHT);

    sun = new hgeSprite(texObjects, 81, 0, 114, 114);
    sun->SetHotSpot(57, 57);
    moon = new hgeSprite(texObjects, 0, 0, 81, 81);
    moon->SetHotSpot(40, 40);
    star = new hgeSprite(texObjects, 72, 81, 9, 9);
    star->SetHotSpot(5, 5);

    glow = new hgeSprite(texObjects, 128, 128, 128, 128);
    glow->SetHotSpot(64, 64);
    glow->SetBlendMode(BLEND_COLORADD | BLEND_ALPHABLEND | BLEND_NOZWRITE);
    seaglow = new hgeSprite(texObjects, 128, 224, 128, 32);
    seaglow->SetHotSpot(64, 0);
    seaglow->SetBlendMode(BLEND_COLORADD | BLEND_ALPHAADD | BLEND_NOZWRITE);

    // Initialize simulation state

    colWhite.SetHWColor(0xFFFFFFFF);
    time = GetTime();
    speed = 0.0f;

    for (int i = 0; i < NUM_STARS; i++) // star positions
    {
        starX[i] = hge->Random_Float(0, SCREEN_WIDTH);
        starY[i] = hge->Random_Float(0, STARS_HEIGHT);
        starS[i] = hge->Random_Float(0.1f, 0.7f);
    }

    for (int i = 0; i < SEA_SUBDIVISION; i++) // sea waves phase shifts
    {
        seaP[i] = i + hge->Random_Float(-15.0f, 15.0f);
    }

    // Systems are ready now!

    return true;
}


void DoneSimulation() {
    // Delete sprites

    delete seaglow;
    delete glow;
    delete star;
    delete moon;
    delete sun;

    delete sky;
    delete sea;

    // Free texture

    hge->Texture_Free(texObjects);
}


void UpdateSimulation() {
    int i, j;
    float a;
    float pos_x = 0;
    const float cellw = SCREEN_WIDTH / (SEA_SUBDIVISION - 1);
    hgeColor col1, col2;
    uint32_t dwCol1;

    // Update time of day

    if (speed == 0.0f) time = GetTime();
    else {
        time += hge->Timer_GetDelta() * speed;
        if (time >= 24.0f) time -= 24.0f;
    }

    seq_id = static_cast<int>(time / 3);
    seq_residue = time / 3 - seq_id;
    const auto zenith = -(time / 12.0f * M_PI - M_PI_2);

    // Interpolate sea and sky colors

    col1.SetHWColor(skyTopColors[seq[seq_id]]);
    col2.SetHWColor(skyTopColors[seq[seq_id + 1]]);
    colSkyTop = col2 * seq_residue + col1 * (1.0f - seq_residue);

    col1.SetHWColor(skyBtmColors[seq[seq_id]]);
    col2.SetHWColor(skyBtmColors[seq[seq_id + 1]]);
    colSkyBtm = col2 * seq_residue + col1 * (1.0f - seq_residue);

    col1.SetHWColor(seaTopColors[seq[seq_id]]);
    col2.SetHWColor(seaTopColors[seq[seq_id + 1]]);
    colSeaTop = col2 * seq_residue + col1 * (1.0f - seq_residue);

    col1.SetHWColor(seaBtmColors[seq[seq_id]]);
    col2.SetHWColor(seaBtmColors[seq[seq_id + 1]]);
    colSeaBtm = col2 * seq_residue + col1 * (1.0f - seq_residue);

    // Update stars

    if (seq_id >= 6 || seq_id < 2)
        for (auto i1 = 0; i1 < NUM_STARS; i1++) {
            a = 1.0f - starY[i1] / STARS_HEIGHT;
            a *= hge->Random_Float(0.6f, 1.0f);
            if (seq_id >= 6) a *= sinf((time - 18.0f) / 6.0f * M_PI_2);
            else a *= sinf((1.0f - time / 6.0f) * M_PI_2);
            starA[i1] = a;
        }

    // Calculate sun position, scale and colors

    if (seq_id == 2) a = sinf(seq_residue * M_PI_2);
    else if (seq_id == 5) a = cosf(seq_residue * M_PI_2);
    else if (seq_id > 2 && seq_id < 5) a = 1.0f;
    else a = 0.0f;

    colSun.SetHWColor(0xFFEAE1BE);
    colSun = colSun * (1 - a) + colWhite * a;

    a = (cosf(time / 6.0f * M_PI) + 1.0f) / 2.0f;
    if (seq_id >= 2 && seq_id <= 6) {
        colSunGlow = colWhite * a;
        colSunGlow.a = 1.0f;
    }
    else colSunGlow.SetHWColor(0xFF000000);

    sunX = SCREEN_WIDTH * 0.5f + cosf(zenith) * ORBITS_RADIUS;
    sunY = SKY_HEIGHT * 1.2f + sinf(zenith) * ORBITS_RADIUS;
    sunS = 1.0f - 0.3f * sinf((time - 6.0f) / 12.0f * M_PI);
    sunGlowS = 3.0f * (1.0f - a) + 3.0f;

    // Calculate moon position, scale and colors

    if (seq_id >= 6) a = sinf((time - 18.0f) / 6.0f * M_PI_2);
    else a = sinf((1.0f - time / 6.0f) * M_PI_2);
    colMoon.SetHWColor(0x20FFFFFF);
    colMoon = colMoon * (1 - a) + colWhite * a;

    colMoonGlow = colWhite;
    colMoonGlow.a = 0.5f * a;

    moonX = SCREEN_WIDTH * 0.5f + cosf(zenith - M_PI) * ORBITS_RADIUS;
    moonY = SKY_HEIGHT * 1.2f + sinf(zenith - M_PI) * ORBITS_RADIUS;
    moonS = 1.0f - 0.3f * sinf((time + 6.0f) / 12.0f * M_PI);
    moonGlowS = a * 0.4f + 0.5f;

    // Calculate sea glow

    if (time > 19.0f || time < 4.5f) // moon
    {
        a = 0.2f; // intensity
        if (time > 19.0f && time < 20.0f) a *= (time - 19.0f);
        else if (time > 3.5f && time < 4.5f) a *= 1.0f - (time - 3.5f);

        colSeaGlow = colMoonGlow;
        colSeaGlow.a = a;
        seaGlowX = moonX;
        seaGlowSX = moonGlowS * 3.0f;
        seaGlowSY = moonGlowS * 2.0f;
    }
    else if (time > 6.5f && time < 19.0f) // sun
    {
        a = 0.3f; // intensity
        if (time < 7.5f) a *= (time - 6.5f);
        else if (time > 18.0f) a *= 1.0f - (time - 18.0f);

        colSeaGlow = colSunGlow;
        colSeaGlow.a = a;
        seaGlowX = sunX;
        seaGlowSX = sunGlowS;
        seaGlowSY = sunGlowS * 0.6f;
    }
    else colSeaGlow.a = 0.0f;

    // Move waves and update sea color

    for (i = 1; i < SEA_SUBDIVISION - 1; i++) {
        a = float(i) / (SEA_SUBDIVISION - 1);
        col1 = colSeaTop * (1 - a) + colSeaBtm * a;
        dwCol1 = col1.GetHWColor();
        const auto f_time = 2.0f * hge->Timer_GetTime();
        a *= 20;

        for (j = 0; j < SEA_SUBDIVISION; j++) {
            sea->SetColor(j, i, dwCol1);

            const auto dy = a * sinf(
                seaP[i] + (float(j) / (SEA_SUBDIVISION - 1) - 0.5f) * M_PI * 16.0f - f_time
            );
            sea->SetDisplacement(j, i, 0.0f, dy, HGEDISP_NODE);
        }
    }

    dwCol1 = colSeaTop.GetHWColor();
    const auto dw_col2 = colSeaBtm.GetHWColor();

    for (j = 0; j < SEA_SUBDIVISION; j++) {
        sea->SetColor(j, 0, dwCol1);
        sea->SetColor(j, SEA_SUBDIVISION - 1, dw_col2);
    }

    // Calculate light path

    if (time > 19.0f || time < 5.0f) // moon
    {
        a = 0.12f; // intensity
        if (time > 19.0f && time < 20.0f) a *= (time - 19.0f);
        else if (time > 4.0f && time < 5.0f) a *= 1.0f - (time - 4.0f);
        pos_x = moonX;
    }
    else if (time > 7.0f && time < 17.0f) // sun
    {
        a = 0.14f; // intensity
        if (time < 8.0f) a *= (time - 7.0f);
        else if (time > 16.0f) a *= 1.0f - (time - 16.0f);
        pos_x = sunX;
    }
    else a = 0.0f;

    if (a != 0.0f) {
        const auto k = static_cast<int>(floorf(pos_x / cellw));
        float s1 = (1.0f - (pos_x - k * cellw) / cellw);
        float s2 = (1.0f - ((k + 1) * cellw - pos_x) / cellw);

        if (s1 > 0.7f) s1 = 0.7f;
        if (s2 > 0.7f) s2 = 0.7f;

        s1 *= a;
        s2 *= a;

        for (i = 0; i < SEA_SUBDIVISION; i += 2) {
            a = sinf(float(i) / (SEA_SUBDIVISION - 1) * M_PI_2);

            col1.SetHWColor(sea->GetColor(k, i));
            col1 += colSun * s1 * (1 - a);
            col1.Clamp();
            sea->SetColor(k, i, col1.GetHWColor());

            col1.SetHWColor(sea->GetColor(k + 1, i));
            col1 += colSun * s2 * (1 - a);
            col1.Clamp();
            sea->SetColor(k + 1, i, col1.GetHWColor());
        }
    }
}


void RenderSimulation() {
    // Render sky

    sky->SetColor(colSkyTop.GetHWColor(), 0);
    sky->SetColor(colSkyTop.GetHWColor(), 1);
    sky->SetColor(colSkyBtm.GetHWColor(), 2);
    sky->SetColor(colSkyBtm.GetHWColor(), 3);
    sky->Render(0, 0);

    // Render stars

    if (seq_id >= 6 || seq_id < 2)
        for (int i = 0; i < NUM_STARS; i++) {
            star->SetColor((uint32_t(starA[i] * 255.0f) << 24) | 0xFFFFFF);
            star->RenderEx(starX[i], starY[i], 0.0f, starS[i]);
        }

    // Render sun

    glow->SetColor(colSunGlow.GetHWColor());
    glow->RenderEx(sunX, sunY, 0.0f, sunGlowS);
    sun->SetColor(colSun.GetHWColor());
    sun->RenderEx(sunX, sunY, 0.0f, sunS);

    // Render moon

    glow->SetColor(colMoonGlow.GetHWColor());
    glow->RenderEx(moonX, moonY, 0.0f, moonGlowS);
    moon->SetColor(colMoon.GetHWColor());
    moon->RenderEx(moonX, moonY, 0.0f, moonS);

    // Render sea

    sea->Render(0, SKY_HEIGHT);
    seaglow->SetColor(colSeaGlow.GetHWColor());
    seaglow->RenderEx(seaGlowX, SKY_HEIGHT, 0.0f, seaGlowSX, seaGlowSY);
}
